常量
C++提供了用户定义常量的概念,const就是为了直接表述“不变化的值”这样一个概念。这种东西在一些环境中非常有用,例如,许多对象在初始化之后就不再改变自己的值了;与直接将文字量散步在代码中相比,采用符号常量写出的代码更容易维护;指针常常是边读边移动,而不是边写边移动;许多函数参数是只读不写的。
关键字const可以加到一个对象的声明上,将这个对象声明为一个常量。因为不允许赋值,常量就必须进行初始化。例如,
const int model = 90; // model是个常量
const int v[] = { 1, 2, 3, 4 }; // v[i]是常量
const int x; // 错误❌:没有初始化
将某些东西声明为常量,就保证了在其作用域内不能改变它们的值:
void f()
{
model = 200; // 错误❌
v[2]++; // 错误❌
}
请注意,const实际上改变了类型,也就是说,它限制了对象能够使用的方式,并没有描述常量应该如何分配。例如,
void g(const X* p)
{
// 在这里不能修改 *p
}
void h()
{
X val; // val可以被修改
g(&val);
// ...
}
编译器可以以多种方式利用一个对象是常量的这一性质,当然,这实际上要看它有多么聪明。例如,对于常量的初始式常常是(也并不总是)常量表达式(C.5节);如果确实是这样,那么这个常量就可以在编译时进行求值。进一步说,如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。例如,
const int c1 = 1;
const int c2 = 2;
const int c3 = my_f(3); // 编译时不知道c3的值
extern const int c4; // 编译时不知道c4的值
const int* p = &c2; // 需要为c2分配空间
在这里,编译器能够知道c1和c2的值,所以就可以将这些值用到常量表达式里。由于在编译时无法知道c3和c4的值(仅仅通过位于这个编译单元中的信息,见9.1节),因此必须为c3和c4分配存储。又由于c2的地址被取用(而且应该假定它会在其他地方用),所以c2也需要分配存储。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储,c1就属于这种情况。关键字extern说明c4是在其他地方定义的(9.2节)。
对于常量的数组,典型的情况是需要分配存储,因为一般说编译器无法弄清楚表达式里使用的是数组中的哪些元素。但无论如何,在许多机器上,甚至对这种情况也可以通过把常量的数组放进只读存储器里,并因此得到效率的改善。
const的最常见用途是作为数组的界和作为分情况标号。例如,
const int a = 42;
const int b = 99;
const int max = 128;
int v[max];
void f(int i)
{
switch(i)
{
case a:
// ...
case b:
// ...
}
}
对于这类情况,人们也常用枚举符(4.8节)来代替const。
关于const用到类成员函数上的使用方式将在第10.2.6节和第10.2.7节讨论。
应该在程序里系统化地使用符号常量,以避免代码中“神秘的数值”。如果某个数值常量代码中反复出现,例如某个数组的界,要修改这些代码就可能变得很麻烦,因为要完成一次正确的更新,该常量的每一次出现都必须修改。采用符号常量可以使信息局部化。常见情况是,一个数值常量代表着程序中的一个假设。例如,4可能代表一个整数的字节数,128可能代表需要缓存的输入字符个数,6.24可能代表丹麦货币与美元的兑换比率。让这些数值常量散布在代码中,对程序的维护者而言这些值是很难辨认和理解的。常见的情况是,在程序移植时,或者当某些其他修改破坏了它们所表示的假设时,由于没有注意到这种数值,以致使它们变成了程序里的错误。将这种假设用加有良好注释的符号常量表示,就可以大大减少这一类维护问题。
🔚